/* Kernel module to addr_range parameters. */

/* (C) 2013 LeadSec
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/semaphore.h>
#include <linux/init.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/errno.h>
#include <linux/skbuff.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/netfilter.h>
#include <net/ip.h>

#include "class_core.h"
#include "addrobj.h"

MODULE_LICENSE( "GPL" );
MODULE_AUTHOR( "LeadSec" );
MODULE_DESCRIPTION( "addr_range object kernel module" );

static addr_type_t addr_head[ADDR_TYPE_MAX];
DEFINE_RWLOCK( addr_range_lock );

static int 
addr_range_get_idx( u8 family )
{
	int idx = 0;
	
	switch ( family ) {
	case NFPROTO_IPV4:
		idx = ADDR_IPV4;
		break;
	case NFPROTO_IPV6:
		idx = ADDR_IPV6;
		break;
	case NFPROTO_ARP:
	case NFPROTO_BRIDGE:
	case NFPROTO_DECNET:
		break;

	default:
		break;
	}

	return idx;
}

/* get and put for addr_range_item refcnt */
static void
addr_range_item_get( addr_range_item_t *item )
{
	if ( item ) atomic_inc( &item->refcnt );
	return ;
}

static void
addr_range_item_put( addr_range_item_t *item )
{
	if ( item && atomic_dec_and_test(&item->refcnt) ) {
		list_del( &item->list );
		atomic_dec( &addr_head[item->index].cfg.cur_size );
		duprintf( "Error! free addr_range_item_t 0x%p, refcnt %d, number %d\n", item, atomic_read(&item->refcnt), atomic_read(&item->num) );
		netobj_kfree( item, item->size );
		item = NULL;
	}
	return ;
}

static int
addr_range_item_destroy( void *item )
{
	addr_range_item_t *node = ( addr_range_item_t * )item;
	addr_range_cell_t *cell, *n;

#ifdef SEQ_LOCK
	write_seqlock_bh(&node->lock);
#else
	write_lock_bh( &node->lock );
#endif	
	list_for_each_entry_safe( cell, n, &node->head, list ) {
		if ( cell )
			cell->destroy( cell, item );
	}
#ifdef SEQ_LOCK
	write_sequnlock_bh(&node->lock);
#else
	write_unlock_bh( &node->lock );	
#endif
	return 0;
}

static void
addr_range_cell_destroy( void *c, void *node )
{
	addr_range_cell_t *cell = ( addr_range_cell_t * )c;
	addr_range_item_t *item = ( addr_range_item_t * )node;
	
	if ( cell && atomic_dec_and_test(&cell->refcnt) ) {
		list_del( &cell->list );
		atomic_dec( &item->num );
		netobj_kfree( cell, cell->size );
		addr_range_item_put( node );
		cell = NULL;
	}
	return ;
 }

static addr_range_cell_t *
addr_range_cell_init( addr_range_item_t *item, addr_range_request_t *req )
{
	addr_range_cell_t *cell = NULL;
	size_t len = 0;
	
	atomic_inc( &item->num );
	if ( addr_head[item->index].cfg.max_size &&
	   unlikely(atomic_read(&item->num) > 
	   	    addr_head[item->index].cfg.max_number) ) {
		duprintf( "table full, droping request!\n" );
		atomic_dec( &item->num );
		return NULL;
	}
	
	len = sizeof( addr_range_cell_t );
	cell = netobj_kzalloc( len, GFP_ATOMIC );
	if ( NULL == cell ) {
		atomic_dec( &item->num );
		return NULL;
	}
		
	cell->size = len;
	cell->index = item->index;
	cell->flags = req->flags;
	cell->range.startip = req->range.startip;
	cell->range.endip = req->range.endip;
	cell->destroy = &addr_range_cell_destroy;
	
	rwlock_init( &cell->lock );
	INIT_LIST_HEAD( &cell->list );

#ifdef SEQ_LOCK
	write_seqlock_bh(&item->lock);
#else
	write_lock_bh( &item->lock );
#endif
	list_add( &cell->list, &item->head );
	atomic_set( &cell->refcnt, 1 );	
#ifdef SEQ_LOCK
	write_sequnlock_bh(&item->lock);
#else
	write_unlock_bh( &item->lock );
#endif

	addr_range_item_get( item );
	return cell;
}

/* Socketopt operation functions */
static addr_range_item_t *
addr_range_lookup_item( addr_range_request_t *req, u8 create )
{
	addr_range_item_t *item = NULL;
	size_t len;
	int family = addr_range_get_idx( req->family );

	read_lock( &addr_range_lock );
	list_for_each_entry( item, &addr_head[family].head, list ) {
		if ( item && (0 == strncmp(item->name, req->name, MAX_OBJ_NAME_LEN)) ) {
			read_unlock( &addr_range_lock );
			return item;
		}
	}
	read_unlock( &addr_range_lock );

	if ( !create )
		return NULL;
	
	/* Create addr_range_item struct */
	atomic_inc( &addr_head[family].cfg.cur_size );
	if ( addr_head[family].cfg.max_size &&
	   unlikely(atomic_read(&addr_head[family].cfg.cur_size) > 
	   	    addr_head[family].cfg.max_size) ) {
		duprintf( "table full, droping request!\n" );
		atomic_dec( &addr_head[family].cfg.cur_size );
		return NULL;
	}

	len = sizeof( addr_range_item_t );
	item = (addr_range_item_t *)netobj_kzalloc( len, GFP_ATOMIC );
	if ( NULL == item ) {
		duprintf( "Alloc memory for addr_range_item_t failed!\n" );
		atomic_dec( &addr_head[family].cfg.cur_size );
		return NULL;
	}
	
	item->size = len;
	item->family = req->family;
	item->index = addr_range_get_idx( item->family );	/* addr type index */
	item->destroy = &addr_range_item_destroy;
	strncpy( item->name, req->name, MAX_OBJ_NAME_LEN );
#ifdef SEQ_LOCK
	seqlock_init(&item->lock);
#else
	rwlock_init( &item->lock );
#endif
	atomic_set( &item->refcnt, 1 );
	atomic_set( &item->num, 0 );
	INIT_LIST_HEAD( &item->head );
	INIT_LIST_HEAD(&item->list);

	write_lock_bh( &addr_range_lock );
	list_add( &item->list, &addr_head[family].head );
	write_unlock_bh( &addr_range_lock );

	return item;
}
	
int
addr_range_construct_item( addr_range_request_t *req )
{
	int ret = 0;
	addr_range_item_t *item = NULL;

	item = addr_range_lookup_item( req, 1 );
	if ( NULL == item ) { 
		duprintf( "Create ipaddr object(%s) failed!\n", req->name );
		ret = -ENOMEM;
	}

	return ret;
}

int
addr_range_destruct_item( addr_range_request_t * req )
{
	addr_range_item_t *item = NULL;
	addr_range_cell_t *cell = NULL, *n = NULL;
	
	int ret = 0;
	item = addr_range_lookup_item( req, 0 );
	if ( NULL == item ) {
		ret = -EEXIST;
		goto out;
	}

#ifdef SEQ_LOCK
	write_seqlock_bh(&item->lock);
#else
	write_lock_bh( &item->lock );
#endif	
	list_for_each_entry_safe( cell, n, &item->head, list ) {
		if ( cell )
			cell->destroy(cell, item);
	}
#ifdef SEQ_LOCK
	write_sequnlock_bh(&item->lock);
#else
	write_unlock_bh( &item->lock );
#endif	

	if ( 1 == atomic_read(&item->refcnt) ) {
		ret = 0;
		addr_range_item_put( item );
	} else 
		ret = -EBUSY;

out:
	return ret;
}

int
addr_range_construct_cell( addr_range_request_t *req )
{
	addr_range_item_t *item = NULL;
	addr_range_cell_t *cell = NULL;
	int ret = 0;

	item = addr_range_lookup_item( req, 0 );
	if ( NULL == item ) {
		ret = -EEXIST;
		goto out;
	}


	if ( item->family != req->family ) {
		duprintf( "request family no match to item family %u!=%u!\n", item->family, req->family );
		ret = -EINVAL;
		goto out;
	}

	cell = addr_range_cell_init( item, req );
	if ( NULL == cell ) 
		ret = -ENOMEM;


out:
	return ret;
}

int 
addr_range_destruct_cell( addr_range_request_t *req )
{
	int ret = 0;
	addr_range_item_t *item = NULL;
	addr_range_cell_t *cell = NULL, *n = NULL;
	
	item = addr_range_lookup_item( req, 0 );
	if ( NULL == item ) {
		ret = -EEXIST;
		goto out;
	}

#ifdef SEQ_LOCK
	write_seqlock_bh(&item->lock);
#else
	write_lock_bh( &item->lock );
#endif	
	list_for_each_entry_safe( cell, n,  &item->head, list ) {
		if ( cell && 
		    0 == memcmp(&cell->range, &req->range, sizeof(req->range)) ) {
			cell->destroy( (void*)cell, (void*)item );
			break;
		}
	}
#ifdef SEQ_LOCK
	write_sequnlock_bh(&item->lock);
#else
	write_unlock_bh( &item->lock );
#endif	

out:
	return ret;
}

int 
addr_range_flush_item( addr_range_request_t *req )
{
	int ret = -1;
	addr_range_item_t *item, *n = NULL;
	int idx = addr_range_get_idx( req->family );

	write_lock_bh( &addr_range_lock );
	list_for_each_entry_safe( item, n, &addr_head[idx].head, list ) {
		if ( item && strncmp(item->name, req->name, MAX_OBJ_NAME_LEN) == 0 ) {
			item->destroy( item );
			ret = 0;
			break;
		}
	}
	write_unlock_bh( &addr_range_lock );
	return ret;
}

/* sockopt get functions */
int
addr_range_exist_item( addr_range_request_t *req )
{
	int ret = 0;
	addr_range_item_t *item;
	int idx = addr_range_get_idx( req->family );

	read_lock_bh( &addr_range_lock );
	list_for_each_entry( item, &addr_head[idx].head, list ) {
		if ( strncmp(item->name, req->name, MAX_OBJ_NAME_LEN) == 0 ) {
			ret = 1;
			break;
		}
	}
	read_unlock_bh( &addr_range_lock );
	return ret;
}

/* Class item operation functions */

addr_range_item_t *
addr_range_find_obj( const char *name, unsigned char flags )
{
	addr_range_item_t *node = NULL;
	int idx = addr_range_get_idx( flags );

	read_lock_bh( &addr_range_lock );
	list_for_each_entry( node, &addr_head[idx].head, list ) {
		if ( node && strncmp(node->name, name, MAX_OBJ_NAME_LEN) == 0 ) {
			read_unlock_bh( &addr_range_lock );
			return node;
		}
	}
	read_unlock_bh( &addr_range_lock );
	return NULL;
}

void
addr_range_bind_obj( unsigned long addr )
{
	addr_range_item_t *node = ( addr_range_item_t * )addr;
	if ( node )
		addr_range_item_get( node );
	return ;
}

void
addr_range_release_obj( unsigned long obj_addr )
{
	addr_range_item_t *node = NULL;
	
	if ( obj_addr == 0 ) return ;
	node = ( addr_range_item_t * )obj_addr;
	addr_range_item_put( node );
	return ;
}

/* clean all of resource for module fint  */

int
addr_range_clean_resource( void )
{
	addr_range_item_t *item, *next;
	int ret = 0;
	int i = 0;

	write_lock_bh( &addr_range_lock );
	for ( i = 0; i < ADDR_TYPE_MAX; i++ ) {
		list_for_each_entry_safe( item, next, &addr_head[i].head, list ) {
			if ( item && item->destroy(item) )
				;
			if ( 1 != atomic_read(&item->refcnt) ) {
				ret = -EBUSY;
				break;
			}
		addr_range_item_put( item );
		}
	}
	write_unlock_bh( &addr_range_lock );

	return ret;
}

/* Match  */

static inline int
iprange_ipv6_sub( const struct in6_addr *a, const struct in6_addr *b )
{
	unsigned int i;
	int r;

	for (i = 0; i < 4; ++i) {
		r = ntohl(a->s6_addr32[i]) - ntohl(b->s6_addr32[i]);
		if (r != 0)
			return r;
	}

	return 0;
}

#ifdef SEQ_LOCK
int
addr_range_matchv4_func( unsigned long obj_addr, const u32 *ip4 )
{
	int ret = 0;
	addr_range_item_t *item = ( addr_range_item_t * )obj_addr;
	addr_range_cell_t *cell = NULL;
	u32 ip = *ip4;
	bool m = 0;
	unsigned seq;

	do {
		seq = read_seqbegin( &item->lock );
		if ( item->family != NFPROTO_IPV4) {
			ret = 0;
			goto out;
		}

		list_for_each_entry( cell, &item->head, list ) {
			if ( !cell )
				break;
			m  = ntohl(ip) < ntohl(cell->range.startip.ip);
			m |= ntohl(ip) > ntohl(cell->range.endip.ip) ;
			m ^= !!(cell->flags & ADDROBJ_INV);
			if ( !m ) {
				ret = 1;
				break;
			}
		}

	} while (read_seqretry(&item->lock, seq));

out:
	return ret;
}
#else	/* SEQ_LOCK */
int
addr_range_matchv4_func( unsigned long obj_addr, const u32 *ip4 )
{
	int ret = 0;
	addr_range_item_t *item = ( addr_range_item_t * )obj_addr;
	addr_range_cell_t *cell = NULL;
	u32 ip = *ip4;
	bool m = 0;

	if ( obj_addr == 0 ) {
		printk( "Bingo!!!!!!!!!!!!!!!!!!!!!!!!\n" );
		return 1;
	}

	write_lock_bh( &item->lock );
	if ( item->family != NFPROTO_IPV4) {
		ret = 0;
		goto out;
	}

	list_for_each_entry( cell, &item->head, list ) {
		if ( !cell )
			break;
		m  = ntohl(ip) < ntohl(cell->range.startip.ip);
		m |= ntohl(ip) > ntohl(cell->range.endip.ip) ;
		m ^= !!(cell->flags & ADDROBJ_INV);
		if ( !m ) {
			ret = 1;
			break;
		}
	}

out:	
	write_unlock_bh( &item->lock );
	return ret;
}
#endif	/* SEQ_LOCK */

#ifdef SEQ_LOCK
int
addr_range_matchv6_func( unsigned long obj_addr, const struct in6_addr *ip6 )
{
	int match = 1;

	return match;
}
#else
int
addr_range_matchv6_func( unsigned long obj_addr, const struct in6_addr *ip6 )
{
	int ret = 0;
	addr_range_item_t *item = ( addr_range_item_t * )obj_addr;
	addr_range_cell_t *cell = NULL;
	bool m = 0;
	
	read_lock_bh( &item->lock );
	if ( item->family != NFPROTO_IPV6) {
		ret = 0;
		goto out;
	}
	
	list_for_each_entry( cell, &item->head, list ) {
		m  = iprange_ipv6_sub(ip6, &cell->range.startip.in6) < 0;
		m |= iprange_ipv6_sub(ip6, &cell->range.endip.in6 ) > 0;
		m ^= !!(cell->flags & ADDROBJ_INV);
		if ( !m ) {
			ret = 1;
			break;
		}
	}
	read_unlock_bh( &item->lock );

out:
	return ret;
}
#endif

/* Seq file and sysctl for Debug configureation */

#ifdef CONFIG_PROC_FS

static void *
addrange_obj_seq_start( struct seq_file *seq, loff_t *pos )
{
	loff_t n = *pos;
	if ( !n ) 
		seq_puts( seq, "addr object list:\n" );

	read_lock_bh( &addr_range_lock );
	return *pos >= ADDR_TYPE_MAX ? NULL : pos;
}

static void *
addrange_obj_seq_next( struct seq_file *seq, void *v, loff_t *pos )
{
	(*pos)++;
	return *pos >= ADDR_TYPE_MAX ? NULL : pos;
}

static void 
addrange_obj_seq_stop( struct seq_file *seq, void *v )
{
	read_unlock_bh( &addr_range_lock );
	return;
}

static int 
addrange_obj_seq_show( struct seq_file *seq, void *v )
{
	addr_range_item_t *item = NULL;
	addr_range_cell_t *cell = NULL;
	
	loff_t *pos = v;
	if( v == NULL ) {
		return 0;
	}
	
	list_for_each_entry_reverse( item, &addr_head[*pos].head, list ) {
		seq_printf( seq, "Name: %s max_s %d cur_s %d refcnt %d\n", item->name, 
				     addr_head[*pos].cfg.max_number, atomic_read(&item->num), 
				     atomic_read(&item->refcnt) );
		
		list_for_each_entry_reverse( cell, &item->head, list ) {
			seq_printf(seq, "ipv4\tmin=%pI4 max=%pI4  refcnt=%d ",
					    &cell->range.startip.ip, &cell->range.endip.ip, 
					    atomic_read(&cell->refcnt) );
			if ( cell->flags & ADDROBJ_INV )
				seq_printf( seq, "inv=yes\n" );
			else
				seq_printf( seq, "inv=no\n" );
		}
		seq_printf( seq, "\n" );
	}
	return 0;
}

static const struct seq_operations addrange_obj_seq_ops = {
	.start          = addrange_obj_seq_start,
	.next           = addrange_obj_seq_next,
	.stop           = addrange_obj_seq_stop,
	.show         	= addrange_obj_seq_show,
};

static int 
addrange_obj_seq_open( struct inode *inode, struct file *file )
{
	return seq_open( file, &addrange_obj_seq_ops );
}

const struct file_operations addrange_obj_list_fops = {
	.owner          = THIS_MODULE,
	.open		= addrange_obj_seq_open,
	.read         	= seq_read,
	.llseek         = seq_lseek,
	.release        = seq_release,
};

struct proc_dir_entry *proc_addr_range;
static int __net_init 
addrange_net_init( struct net *net )
{
	proc_addr_range = proc_mkdir( "addr_range", proc_netobj );
	if ( !proc_addr_range )
		return -ENOMEM;

	if ( !proc_create_data( "addr_list", S_IRUGO, proc_addr_range, &addrange_obj_list_fops, NULL ) )
		goto remove_addr_range;
	proc_symlink( "cur_ipv4_count", proc_addr_range, PROC_PATH"/cur_ipv4_count" );
	proc_symlink( "max_ipv4_size", proc_addr_range, PROC_PATH"/max_ipv4_size"  );
	proc_symlink( "max_ipv4_number", proc_addr_range, PROC_PATH"/max_ipv4_number" );

	proc_symlink( "max_ipv6_size", proc_addr_range, PROC_PATH"/max_ipv6_size" );
	proc_symlink( "cur_ipv6_count", proc_addr_range, PROC_PATH"/cur_ipv6_count" );
	proc_symlink( "max_ipv6_number", proc_addr_range, PROC_PATH"/max_ipv6_number" );
	return 0;
	
remove_addr_range:
	remove_proc_entry( "addr_range", proc_netobj );
	return -ENOMEM;
}

static void __net_exit 
addrange_net_exit( struct net *net )
{
	remove_proc_entry( "cur_ipv4_count", proc_addr_range );
	remove_proc_entry( "max_ipv4_size", proc_addr_range );
	remove_proc_entry( "max_ipv4_number", proc_addr_range );

	remove_proc_entry( "max_ipv6_size", proc_addr_range );
	remove_proc_entry( "cur_ipv6_count", proc_addr_range );
	remove_proc_entry( "max_ipv6_number", proc_addr_range );

	remove_proc_entry( "addr_list", proc_addr_range );
	remove_proc_entry( "addr_range",proc_netobj  );
	return ;
}

static struct pernet_operations addrange_net_ops = {
	.init = addrange_net_init,
	.exit = addrange_net_exit,
};

int
addrange_obj_readproc_init( void )
{
	return register_pernet_subsys( &addrange_net_ops );
}

void
addrange_obj_readproc_exit( void )
{
	unregister_pernet_subsys( &addrange_net_ops );
}

#else	/* CONFIG_PROC_FS */

int
addrange_obj_readproc_init( void )
{
	return 0;
}

void
addrange_obj_readproc_exit( void )
{
	return ;
}

#endif	/* CONFIG_PROC_FS */

#ifdef CONFIG_SYSCTL
struct ctl_table_header *addrange_obj_sysctl_header = NULL;

static ctl_table addrange_obj_sysctl_table[] = {
	{
		.procname		= "max_ipv4_size",
		.data			= &addr_head[ADDR_IPV4].cfg.max_size,
		.maxlen			= sizeof( int ),
		.mode			= 0644,
		.proc_handler	= &proc_dointvec,
		
	},
	{
		.procname		= "max_ipv4_number",
		.data			= &addr_head[ADDR_IPV4].cfg.max_number,
		.maxlen			= sizeof( int ),
		.mode			= 0644,
		.proc_handler	= &proc_dointvec,
		
	},
	{
		.procname		= "cur_ipv4_count",
		.data			= &addr_head[ADDR_IPV4].cfg.cur_size,
		.maxlen			= sizeof( int ),
		.mode			= 0444,
		.proc_handler		= &proc_dointvec,
		
	},
	{
		.procname		= "max_ipv6_size",
		.data			= &addr_head[ADDR_IPV6].cfg.max_size,
		.maxlen			= sizeof( int ),
		.mode			= 0644,
		.proc_handler	= &proc_dointvec,
		
	},
	{
		.procname		= "max_ipv6_number",
		.data			= &addr_head[ADDR_IPV6].cfg.max_number,
		.maxlen			= sizeof( int ),
		.mode			= 0644,
		.proc_handler	= &proc_dointvec,
		
	},
	{
		.procname		= "cur_ipv6_count",
		.data			= &addr_head[ADDR_IPV6].cfg.cur_size,
		.maxlen			= sizeof( int ),
		.mode			= 0444,
		.proc_handler		= &proc_dointvec,
		
	},

	{ }
};

static ctl_table addrange_obj_chiledir_table[] = {
	{
		.procname	= "addr_range",
		.mode		= 0555,
		.child		= addrange_obj_sysctl_table,
	},
	{ }
};

static ctl_table addrange_obj_netobj_table[] = {
	{
		.procname	= "netobj",
		.mode		= 0555,
		.child		= addrange_obj_chiledir_table,
	},
	{ }
};

static ctl_table addrange_obj_root_table[] = {
	{
		.procname	= "leadsec",
		.mode		= 0555,
		.child		= addrange_obj_netobj_table,
	},
	{ }
};

static int addrange_obj_sysctl_init( void )
{
	addrange_obj_sysctl_header = register_sysctl_table( addrange_obj_root_table );
	if ( NULL == addrange_obj_sysctl_header )
		return -ENOMEM;

	return 0;
}

static void addrange_obj_sysctl_fint( void )
{
	unregister_sysctl_table( addrange_obj_sysctl_header );
	return ;
}

#else	/* CONFIG_SYSCTL */

static int addrange_obj_sysctl_init( void )
{
	return 0;
}

static void addrange_obj_sysctl_fint( void )
{
	return ;
}

#endif	/* CONFIG_SYSCTL */

int addrange_obj_proc_init( void )
{
	int ret = 0;

	ret = addrange_obj_sysctl_init();
	if ( 0 != ret )
		return ret;

	ret = addrange_obj_readproc_init();
	if ( 0 != ret )
		goto unreg_sysctl;

	return ret;
unreg_sysctl:
	addrange_obj_sysctl_fint();
	return ret;
}

void addrange_obj_proc_fint( void )
{
	addrange_obj_readproc_exit();
	addrange_obj_sysctl_fint();
	return ;
}

/* Initiation moudles */
int
addr_range_list_init( void )
{
	int i = 0;

	for ( i = 0; i < ADDR_TYPE_MAX; i++ )
		INIT_LIST_HEAD( &addr_head[i].head );

	addr_head[ADDR_IPV4].cfg.max_number = 4096;
	addr_head[ADDR_IPV4].cfg.max_size = 8096;
	atomic_set( &addr_head[ADDR_IPV4].cfg.cur_size, 0 );

	addr_head[ADDR_IPV6].cfg.max_number = 4096;
	addr_head[ADDR_IPV6].cfg.max_size = 8096;
	atomic_set( &addr_head[ADDR_IPV6].cfg.cur_size, 0 );
	return 0;
}
EXPORT_SYMBOL( addr_range_list_init );

void
addr_range_list_free( void )
{
	return ;
}

EXPORT_SYMBOL( addr_range_list_free );

EXPORT_SYMBOL( addr_range_construct_item );
EXPORT_SYMBOL( addr_range_destruct_item );
EXPORT_SYMBOL( addr_range_construct_cell );
EXPORT_SYMBOL( addr_range_destruct_cell );
EXPORT_SYMBOL( addr_range_flush_item );
EXPORT_SYMBOL( addr_range_exist_item );

EXPORT_SYMBOL( addr_range_clean_resource );
EXPORT_SYMBOL( addr_range_find_obj );
EXPORT_SYMBOL( addr_range_bind_obj );

EXPORT_SYMBOL( addr_range_release_obj );
EXPORT_SYMBOL( addr_range_matchv4_func );
EXPORT_SYMBOL( addr_range_matchv6_func );
EXPORT_SYMBOL( addrange_obj_proc_init );
EXPORT_SYMBOL( addrange_obj_proc_fint );

